/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cuatroenraya.jugadores;
import cuatroenraya.Jugador;

import java.util.Iterator;
import java.util.LinkedList;
//import javax.swing.*;

/**
 *
 * @author David
 */
public class JugadorAlumnoG26 extends Jugador{

    private int estadoTablero[][];
    private int nroJugada;
    private int filaJugada;
    private int columnaJugada;
    
    private int UltimaJugadaPropia[];
    private int AnteUlitmaJugadaPropia[];

    private int UltimaJugadaOponente[];
    private int AnteUlitmaJugadaOponente[];

    private static final int LARGO_LINEA_OBJETIVO=4;
    private LinkedList <Metricas> MetricasOponente;
    private LinkedList <Metricas> MetricasPropias;
    private Metricas mtrOptima;

    public JugadorAlumnoG26() {

        super.numeroJugador = 1 ;
        UltimaJugadaPropia = new int[2];
        AnteUlitmaJugadaPropia = new int[2];
        UltimaJugadaOponente = new int[2];
        AnteUlitmaJugadaOponente = new int[2];
    }

    public int[] jugar(int tablero[][])
    {
        
        if(estadoTablero != null)
        { 
            AnteUlitmaJugadaOponente = UltimaJugadaOponente;
            UltimaJugadaOponente = DeterminarUltimaJugada(tablero);
            AnalizarEstados(tablero);
            mtrOptima = BuscarHeuristicaOptima();
            RealizarMovimiento(tablero);
        }
        else
        {
            //PRIMERA JUGADA QUE VA REALIZAR
            estadoTablero = new int[8][8];
            RealizarMovimiento(tablero);
        }

        //Copio el tablero para poder determinar, en mi proximo turno, cual fue la jugada del oponente
        CopiarTablero(tablero);
       // System.arraycopy(tablero, 0, estadoTablero, 0, tablero.length);
        
        return UltimaJugadaPropia;
    }

/* Determina la ultima jugada realizada por el oponente
 * para poder determinar las metricas y los estados posibles 
 * para realizar una jugada optima.
 */

    private int[] DeterminarUltimaJugada(int[][] tablero) {
        int[] jugadaRealizada = new int[2];
        for(int i=0;i<7;i++)
        {
            for(int j=0;j<7;j++)
            {
                if(tablero[i][j]!=estadoTablero[i][j] && !(filaJugada == i && columnaJugada == j))
                {
                    jugadaRealizada[0] = i;
                    jugadaRealizada[1] = j;
                }
            }
        }
        return jugadaRealizada;

    }

    private void AnalizarEstados(int[][] tablero) {
        
        
        //Determinamos la distancia al estado final, de los estados posibles, de las 2 ultimas jugadas.
        MetricasPropias =  new LinkedList();
        DeterminarDistanciaObjetivo(AnteUlitmaJugadaPropia, tablero, MetricasPropias, false);
        DeterminarDistanciaObjetivo(UltimaJugadaPropia, tablero, MetricasPropias, false);


        MetricasOponente =  new LinkedList();
        DeterminarDistanciaObjetivo(AnteUlitmaJugadaOponente, tablero, MetricasOponente, true);
        DeterminarDistanciaObjetivo(UltimaJugadaOponente, tablero, MetricasOponente, true);
       
    }
    
    
    /* Se analiza la vecindad de la casilla para, mejorar la funcion heuristica,
     * ya que un casillero que se encuentre rodeado de fichas, propias, sera mejor 
     * jugada esa linea  que uno que este mas aislado del resto.
     */
    private int DeterminarMetricaPorVecindad(int[][] tablero, int fila, int columna, boolean esOponente)
    {
        int metrica=0;
        int filaInicio=(fila-1 >=0)?fila-1:0;
        int colInicio=(columna-1 >=0)?columna-1:0;
        
        //Restamos si se encuentra en los vordes. 
        metrica = (fila-1 >=0)?0:-1;
        metrica += (columna-1 >=0)?0:-1;
        metrica += (fila+1 <=7)?0:-1;
        metrica += (columna+1 <=7)?0:-1;
        
        for(int i=filaInicio;i<=7 && i<=fila+1 && i>=0;i++)
        {
            for(int j=colInicio;j<7 && j<=columna+1 && j>=0;j++)
            {
                if(i == fila && j== columna)continue;
                
                if((tablero[i][j]==super.numeroJugador && !esOponente) || (tablero[i][j]!=super.numeroJugador && esOponente && tablero[i][j]!=0))
                {
                   metrica+=2;
                }
                else
                {
                  // if((tablero[i][j]!=0))metrica--;
                }
                
            }
        }
        return metrica;
    }
    
    /* Se analiza la distancia al objetivo final de completar las 4 fichas en linea
     * @return una metrica q determina la distacia asi el obejtivo final
     * el numero de jugadas restantes posibles para ganar es una buena metrica
     */

    private int DeterminarDistanciaObjetivo(int[] jugadaRealizada, int[][] tablero, LinkedList listMetricas, boolean esOponente) {

        
        //Para esa fila vertical existen 4 posibilidades de formar una linea, dentro de
        // cada posicion se guarda el tamaño de la linea.
        int [] MetricasVerticales  = new int[4];

        //Para esa fila horizontal existen 4 posibilidades de formar una linea
        int [] MetricasHorizontales  = new int[4];

        //Para esa fila horizontal existen 4 posibilidades de formar una linea
        int [] MetricasDiagonalesAsc  = new int[4];
        int [] MetricasDiagonalesDesc  = new int[4];

        int MetricasVecindad  = 0;
        int ordenFila = -1;
        //Si la fila de la ultima jugada es menor a 3 se empieza a analizar desde la primera
        // fila, caso contrario se comienza a analizar 3 filas antes
        int filaDesde = ((jugadaRealizada[0] - ( LARGO_LINEA_OBJETIVO - 1 )) < 0 ? 0 : (jugadaRealizada[0] - ( LARGO_LINEA_OBJETIVO - 1 )));

        // Hasta donde se analiza, sin pasarce de los limites.
        int filaHasta = ((jugadaRealizada[0] + ( LARGO_LINEA_OBJETIVO - 1 )) > 7 ? 7 : (jugadaRealizada[0] + ( LARGO_LINEA_OBJETIVO - 1 )));

        //Simil
        int ColumnaDesde = ((jugadaRealizada[1] - ( LARGO_LINEA_OBJETIVO - 1 )) < 0 ? 0 : (jugadaRealizada[1] - ( LARGO_LINEA_OBJETIVO - 1 )));

        //Simil
        int ColumnaHasta = ((jugadaRealizada[1] + ( LARGO_LINEA_OBJETIVO - 1 )) > 7 ? 7 : (jugadaRealizada[1] + ( LARGO_LINEA_OBJETIVO - 1 )));
        
        
        //Distancia para realizar fila vertical
        for(int i = filaDesde; (i < filaHasta && i < filaDesde + LARGO_LINEA_OBJETIVO); i++)
        {
            ordenFila++;
            //Se descartan las posibles lineas verticales q no superan los 4 elementos.
            if((i + ( LARGO_LINEA_OBJETIVO - 1 )) > filaHasta)break;
            MetricasVecindad = 0;
            for(int j = i; j < i + LARGO_LINEA_OBJETIVO ; j++)
            {
                //Analizamos la vecindad de ese casillero. Por que ante igual metrica, la que este rodeada de
                //mayor cantidad de jugadas propias, sera mejor q otra mas asilada en el tablero.
                MetricasVecindad += DeterminarMetricaPorVecindad(tablero, j,jugadaRealizada[1], esOponente);
                
                //Para esa fila vertical existen 4 posibilidades de formar una linea
                if(tablero[j][jugadaRealizada[1]]!=0)
                {
                    if((tablero[j][jugadaRealizada[1]]==super.numeroJugador && !esOponente) || (tablero[j][jugadaRealizada[1]]!=super.numeroJugador && esOponente))
                    {

                        MetricasVerticales[ordenFila]++;

                    }
                    else
                    {
                        // Si el oponente tiene ya una ficha, no se puede conseguir el objetivo, en este tramo.
                        // Para ninguno de los dos participante
                        MetricasVerticales[ordenFila] = 0;
                        break;
                    }
                    
                }

            }
            if(MetricasVerticales[ordenFila] > 0)
            {
                Metricas metrica = new Metricas();
                metrica.Heuristica = MetricasVerticales[ordenFila];
                metrica.vecindad = MetricasVecindad;
                metrica.FilaInicio = i;
                metrica.ColumnaInicio = jugadaRealizada[1];
                metrica.tipoFila= metrica.FILA_VERTICAL;
                metrica.esOponente = esOponente;
                listMetricas.add(metrica);
            }
        }
        
        ordenFila = -1;
        //Distancia para realizar fila horizontal
        for(int j = ColumnaDesde; (j < ColumnaHasta && j < ColumnaDesde + LARGO_LINEA_OBJETIVO); j++)
        {
            ordenFila++;
            //Se descartan las posibles lineas horizontales q no superan los 4 elementos.
            if((j + ( LARGO_LINEA_OBJETIVO - 1 )) > ColumnaHasta)break;
            MetricasVecindad = 0;
            for(int i = j; i < j + LARGO_LINEA_OBJETIVO ; i++)
            {
                //Analizamos la vecindad de ese casillero. Por que ante igual metrica, la que este rodeada de
                //mayor cantidad de jugadas propias, sera mejor q otra mas asilada en el tablero.
                MetricasVecindad += DeterminarMetricaPorVecindad(tablero, jugadaRealizada[0],i, esOponente);
                
                if(tablero[jugadaRealizada[0]][i]!=0)
                {
                    if((tablero[jugadaRealizada[0]][i]==super.numeroJugador && !esOponente) || (tablero[jugadaRealizada[0]][i]!=super.numeroJugador && esOponente))
                    {

                        MetricasHorizontales[ordenFila]++;

                    }
                    else
                    {
                        // Si el oponente tiene ya una ficha, no se puede conseguir el objetivo, en este tramo.
                        // Para ninguno de los dos participante
                        MetricasHorizontales[ordenFila] = 0;
                        break;
                    }

                }

            }
            if(MetricasHorizontales[ordenFila] > 0)
            {
                Metricas metrica = new Metricas();
                metrica = new Metricas();
                metrica.Heuristica = MetricasHorizontales[ordenFila];
                metrica.vecindad = MetricasVecindad;
                metrica.FilaInicio = jugadaRealizada[0];
                metrica.ColumnaInicio = j;
                metrica.tipoFila= metrica.FILA_HORIZONTAL;
                metrica.esOponente = esOponente;
                listMetricas.add(metrica);
            }
        }

        ordenFila = -1;
        //Distancia para realizar fila en diagonal Ascendente(/)
        for(int i = filaDesde; (i < filaHasta && i < filaDesde + LARGO_LINEA_OBJETIVO); i++)
        {
            ordenFila++;
            //Se descartan las posibles lineas diagonales q no superan los 4 elementos.
            if((i + ( LARGO_LINEA_OBJETIVO - 1 )) > filaHasta)break;
            
            if(( jugadaRealizada[1] + jugadaRealizada[0] - i - ( LARGO_LINEA_OBJETIVO - 1 ) ) < 0)break;

            if(( jugadaRealizada[1] + jugadaRealizada[0] - i) > ColumnaHasta)continue;
            int ciclo = i;
            MetricasVecindad = 0;
            for(int j = jugadaRealizada[1] - i + jugadaRealizada[0]; (j >= ColumnaDesde && j > ( jugadaRealizada[1] + jugadaRealizada[0] - i) - LARGO_LINEA_OBJETIVO) ; j--)
            {
                //Analizamos la vecindad de ese casillero. Por que ante igual metrica, la que este rodeada de
                //mayor cantidad de jugadas propias, sera mejor q otra mas asilada en el tablero.
                MetricasVecindad += DeterminarMetricaPorVecindad(tablero, ciclo,j, esOponente);
                
                //Para esa fila diagonal existen 4 posibilidades de formar una linea
                if(tablero[ciclo][j]!=0)
                {
                    if((tablero[ciclo][j]==super.numeroJugador && !esOponente) || (tablero[ciclo][j]!=super.numeroJugador && esOponente))
                    {

                        MetricasDiagonalesAsc[ordenFila]++;
                    }
                    else
                    {
                        // Si el oponente tiene ya una ficha, no se puede conseguir el objetivo, en este tramo.
                        // Para ninguno de los dos participante
                        MetricasDiagonalesAsc[ordenFila] = 0;
                        break;
                    }

                }
                ciclo++;
            }
            if(MetricasDiagonalesAsc[ordenFila] > 0)
            {
                Metricas metrica = new Metricas();
                metrica = new Metricas();
                metrica.Heuristica = MetricasDiagonalesAsc[ordenFila];
                metrica.vecindad = MetricasVecindad;
                metrica.FilaInicio = i;
                metrica.ColumnaInicio = jugadaRealizada[1] - i + jugadaRealizada[0];
                metrica.tipoFila= metrica.FILA_DIAGONAL_ASC;
                metrica.esOponente = esOponente;
                listMetricas.add(metrica);
            }
        }

        ordenFila = -1;
        //Distancia para realizar fila en diagonal Descendente(\)
        //for(int i = filaHasta; (i > filaDesde && i > filaHasta - 3); i--)
        for(int i = filaDesde; (i < filaHasta && i < filaDesde + LARGO_LINEA_OBJETIVO); i++)
        {
            ordenFila++;
            //Se descartan las posibles lineas diagonales q no superan los 4 elementos.
            if((i + ( LARGO_LINEA_OBJETIVO - 1 )) > filaHasta)break;
            //if(( ColumnaDesde + i + 3 ) > ColumnaHasta)break;

            if(( jugadaRealizada[1] - jugadaRealizada[0] + i + ( LARGO_LINEA_OBJETIVO - 1 ) ) > ColumnaHasta)break;

            if(( jugadaRealizada[1] - jugadaRealizada[0] + i) < ColumnaDesde)continue;
            int ciclo = i;
            MetricasVecindad = 0;
            for(int j = ( jugadaRealizada[1] - jugadaRealizada[0] + i); (j <= ColumnaHasta && j < ( jugadaRealizada[1] - jugadaRealizada[0] + i) + LARGO_LINEA_OBJETIVO) ; j++)
            {
                //Analizamos la vecindad de ese casillero. Por que ante igual metrica, la que este rodeada de
                //mayor cantidad de jugadas propias, sera mejor q otra mas asilada en el tablero.
                MetricasVecindad += DeterminarMetricaPorVecindad(tablero, ciclo,j, esOponente);
                
                //Para esa fila diagonal existen 4 posibilidades de formar una linea
                if(tablero[ciclo][j]!=0)
                {
                    if((tablero[ciclo][j]==super.numeroJugador && !esOponente) || (tablero[ciclo][j]!=super.numeroJugador && esOponente))
                    {

                        MetricasDiagonalesDesc[ordenFila]++;
                    }
                    else
                    {
                        // Si el oponente tiene ya una ficha, no se puede conseguir el objetivo, en este tramo.
                        // Para ninguno de los dos participante
                        MetricasDiagonalesDesc[ordenFila] = 0;
                        break;
                    }

                }

                ciclo++;
            }
            if(MetricasDiagonalesDesc[ordenFila] > 0)
            {
                Metricas metrica = new Metricas();
                metrica = new Metricas();
                metrica.Heuristica = MetricasDiagonalesDesc[ordenFila];
                metrica.vecindad = MetricasVecindad;
                metrica.FilaInicio = i;
                metrica.esOponente = esOponente;
                metrica.ColumnaInicio = ( jugadaRealizada[1] - jugadaRealizada[0] + i);
                metrica.tipoFila= metrica.FILA_DIAGONAL_DESC;
                listMetricas.add(metrica);
            }
        }


        return -1;
    }

    private void CopiarTablero(int[][] tablero) {

        for(int i=0;i<7;i++)
        {
            for(int j=0;j<7;j++)
            {
                estadoTablero[i][j]=tablero[i][j];
            }
        }
    }

    // Comparo para poder ver si se realiza una accion defensiva u ofensiva.
    // Si la heuristica del oponente, me reporta que se encuentra a un
    // movimiento de ganar, realizo un movimiento defensivo. Caso contrario
    // realizo un movimiento ofensivo.
    private Metricas BuscarHeuristicaOptima() {

        Metricas movimiento = new Metricas();

        for (Iterator<Metricas> it = MetricasOponente.iterator(); it.hasNext();) {
                Metricas metricas = it.next();
                //Se realiza una jugada defensiva
                if(metricas.Heuristica >=2 && movimiento.Heuristica < metricas.Heuristica)
                {
                    movimiento = metricas;
                }
                else if(metricas.Heuristica >=2 && movimiento.Heuristica == metricas.Heuristica && movimiento.vecindad < metricas.vecindad)
                {
                    movimiento = metricas;
                }
        }

        for (Iterator<Metricas> it = MetricasPropias.iterator(); it.hasNext();) {
                Metricas metricas = it.next();
                //Devolver la jugada con mejor heuristica posible, para realizar una ofensiva
                if(metricas.Heuristica >=3 || movimiento.Heuristica < metricas.Heuristica)
                {
                    movimiento = metricas;

                }
                else if(movimiento.Heuristica == metricas.Heuristica && (movimiento.vecindad <= metricas.vecindad || movimiento.esOponente))
                {
                    movimiento = metricas;
                }
                    
        }

        return movimiento;
        
    }

    //Se realiza el movimiento, segun la mejor metrica encontrada
    private void RealizarMovimiento(int[][] tablero) {
        
        AnteUlitmaJugadaPropia = UltimaJugadaPropia;

        if(mtrOptima==null)
        {
            //Se realiza la primer jugada en el centro del tablero
            if(tablero[4][4]==0)
            {
                UltimaJugadaPropia[0] = filaJugada = 3;
                UltimaJugadaPropia[1] = columnaJugada = 3;
            }
            else
            {
                UltimaJugadaPropia[0] = filaJugada = 4;
                UltimaJugadaPropia[1] = columnaJugada = 4;
            }
            return ;
        }
        //Solo para prevenir algo raro
        if(mtrOptima.Heuristica == 0)
        {
            for(int i=0;i<7;i++)
            {
                for(int j=0;j<7;j++)
                {
                    if(tablero[i][j]==0)
                    {
                        UltimaJugadaPropia[0] = filaJugada = i;
                        UltimaJugadaPropia[1] = columnaJugada = j;
                        return;
                    }
                }
            }

        }
        //Fin prevencion
        int metricaMax = -10;
        int metrica = 0;
        int indexFila = 0;
        int indexColumna = 0;
        //Realizo el movimiento optimo.
        switch(mtrOptima.tipoFila)
        {

            case Metricas.FILA_VERTICAL:
                    
                    for (int i = mtrOptima.FilaInicio; i < LARGO_LINEA_OBJETIVO + mtrOptima.FilaInicio; i++) {
                        
                        if(tablero[i][mtrOptima.ColumnaInicio]==0)
                        {
                            metrica = DeterminarMetricaPorVecindad(tablero,i,mtrOptima.ColumnaInicio,mtrOptima.esOponente);
                            if(metrica >= metricaMax)
                            {
                                indexFila = i;
                                metricaMax = metrica;
                            }
                        }
                    }
                        UltimaJugadaPropia[0] = filaJugada = indexFila;
                        UltimaJugadaPropia[1] = columnaJugada = mtrOptima.ColumnaInicio;
                break;
                
            case Metricas.FILA_HORIZONTAL:
                
                    for (int j = mtrOptima.ColumnaInicio; j < LARGO_LINEA_OBJETIVO + mtrOptima.ColumnaInicio; j++) {

                        if(tablero[mtrOptima.FilaInicio][j]==0)
                        {
                            metrica = DeterminarMetricaPorVecindad(tablero,mtrOptima.FilaInicio,j,mtrOptima.esOponente);
                            if(metrica >= metricaMax)
                            {
                                indexColumna = j;
                                metricaMax = metrica;
                            }
                        }
                    }   
                        UltimaJugadaPropia[0] = filaJugada = mtrOptima.FilaInicio;
                        UltimaJugadaPropia[1] = columnaJugada = indexColumna;
                break;
                
            case Metricas.FILA_DIAGONAL_ASC:

                for (int i = 0; i < LARGO_LINEA_OBJETIVO ; i++) {
                        
                    if(tablero[mtrOptima.FilaInicio + i][mtrOptima.ColumnaInicio - i]==0)
                    {
                        metrica = DeterminarMetricaPorVecindad(tablero,mtrOptima.FilaInicio + i,mtrOptima.ColumnaInicio - i,mtrOptima.esOponente);
                        if(metrica >= metricaMax)
                        {
                            indexColumna = mtrOptima.ColumnaInicio - i;
                            indexFila = mtrOptima.FilaInicio + i;
                            metricaMax = metrica;
                        }
                    }
                }      
                        UltimaJugadaPropia[0] = filaJugada = indexFila;
                        UltimaJugadaPropia[1] = columnaJugada = indexColumna;
                break;
                
            case Metricas.FILA_DIAGONAL_DESC:
                
                for (int i = 0; i < LARGO_LINEA_OBJETIVO ; i++) {

                    if(tablero[mtrOptima.FilaInicio + i][mtrOptima.ColumnaInicio + i]==0)
                    {
                        metrica = DeterminarMetricaPorVecindad(tablero,mtrOptima.FilaInicio + i,mtrOptima.ColumnaInicio + i,mtrOptima.esOponente);
                        if(metrica >= metricaMax)
                        {
                            indexColumna = mtrOptima.ColumnaInicio + i;
                            indexFila = mtrOptima.FilaInicio + i;
                            metricaMax = metrica;
                        }
                    }
                }   

                    UltimaJugadaPropia[0] = filaJugada = indexFila;
                    UltimaJugadaPropia[1] = columnaJugada = indexColumna;
                break;
        }
        
    }
    class Metricas
    {
        int FilaInicio;
        int ColumnaInicio;
        int Heuristica;
        int vecindad;
        int tipoFila;
        boolean esOponente = false;
        final static int FILA_VERTICAL=0;
        final static int FILA_HORIZONTAL=1;
        final static int FILA_DIAGONAL_ASC=2; // (/)
        final static int FILA_DIAGONAL_DESC=3; // (\)

        @Override
        public String toString() {
            if(Heuristica <=1)return "";
            String estado="";
                switch(tipoFila)
                {

                    case FILA_VERTICAL:
                        estado ="\nFILA_VERTICAL ";
                        break;
                    case FILA_HORIZONTAL:
                        estado ="\nFILA_HORIZONTAL ";
                        break;
                    case FILA_DIAGONAL_ASC:
                        estado ="\nFILA_DIAGONAL_ASC ";
                        break;
                    case FILA_DIAGONAL_DESC:
                        estado ="\nFILA_DIAGONAL_DESC ";
                        break;
                }
                estado = estado +  "\nFila inicio: " + FilaInicio + "\nColumna inicio: " + ColumnaInicio +
                         "\nHeuristica: " + Heuristica + "\nVecindad: " + vecindad;
            
            return estado;
        }
    }

}
